package com.thx.common.util;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.HashMap;

import javax.crypto.Cipher;

/**
 * 来源于网络：http://blog.csdn.net/yys79/article/details/41514871 RSA公钥加密算法是1977年由罗纳德·李维斯特（Ron
 * Rivest）、阿迪·萨莫尔（Adi Shamir）和伦纳德·阿德曼（Leonard Adleman）一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。
 * RSA是目前最有影响力的公钥加密算法，它能够抵抗到目前为止已知的绝大多数密码攻击，已被ISO推荐为公钥数据加密标准。 今天只有短的RSA钥匙才可能被强力方式解破
 * 。到2008年为止，世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长，用RSA加密的信息实际上是不能被解破的 。但在分布式计算和量子计算机理论日趋成熟的今天
 * ，RSA加密安全性受到了挑战。RSA算法基于一个十分简单的数论事实：将两个大素数相乘十分容易，但是想要对其乘积进行因式分解却极其困难，
 * 因此可以将乘积公开作为加密密钥。
 *
 * @author balancejia
 */
public class RsaUtils {

  /**
   * 生成公钥和私钥.
   */
  public static HashMap<String, Object> getKeys() throws NoSuchAlgorithmException {
    HashMap<String, Object> map = new HashMap<String, Object>();
    KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",
        new org.bouncycastle.jce.provider.BouncyCastleProvider());
    keyPairGen.initialize(1024);
    KeyPair keyPair = keyPairGen.generateKeyPair();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    map.put("public", publicKey);
    map.put("private", privateKey);
    return map;
  }

  /**
   * 使用模和指数生成RSA公钥.
   *
   * @param modulus 模
   * @param exponent 指数
   */
  public static RSAPublicKey getPublicKey(String modulus, String exponent) {
    try {
      BigInteger b1 = new BigInteger(modulus);
      BigInteger b2 = new BigInteger(exponent);
      KeyFactory keyFactory = KeyFactory.getInstance("RSA",
          new org.bouncycastle.jce.provider.BouncyCastleProvider());
      RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2);
      return (RSAPublicKey) keyFactory.generatePublic(keySpec);
    } catch (Exception ex) {
      ex.printStackTrace();
      return null;
    }
  }

  /**
   * 使用模和指数生成RSA私钥.
   * /None/NoPadding
   *
   * @param modulus 模
   * @param exponent 指数
   */
  public static RSAPrivateKey getPrivateKey(String modulus, String exponent) {
    try {
      BigInteger b1 = new BigInteger(modulus);
      BigInteger b2 = new BigInteger(exponent);
      KeyFactory keyFactory = KeyFactory.getInstance("RSA",
          new org.bouncycastle.jce.provider.BouncyCastleProvider());
      RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(b1, b2);
      return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
    } catch (Exception ex) {
      ex.printStackTrace();
      return null;
    }
  }

  /**
   * 公钥加密.
   */
  public static String encryptByPublicKey(String data, RSAPublicKey publicKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA",
        new org.bouncycastle.jce.provider.BouncyCastleProvider());
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    // 模长
    int keyLen = publicKey.getModulus().bitLength() / 8;
    // 加密数据长度 <= 模长-11
    String[] datas = splitString(data, keyLen - 11);
    String mi = "";
    // 如果明文长度大于模长-11则要分组加密
    for (String s : datas) {
      mi += bcd2Str(cipher.doFinal(s.getBytes()));
    }
    return mi;
  }

  /**
   * 私钥解密.
   */
  public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA",
        new org.bouncycastle.jce.provider.BouncyCastleProvider());
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    // 模长
    int keyLen = privateKey.getModulus().bitLength() / 8;
    byte[] bytes = data.getBytes();
    byte[] bcd = asciiToBcd(bytes, bytes.length);
    // System.err.println(bcd.length);
    // 如果密文长度大于模长则要分组解密
    String ming = "";
    byte[][] arrays = splitArray(bcd, keyLen);
    for (byte[] arr : arrays) {
      ming += new String(cipher.doFinal(arr));
    }
    return ming;
  }

  /**
   * ASCII码转BCD码.
   */
  public static byte[] asciiToBcd(byte[] ascii, int ascLen) {
    byte[] bcd = new byte[ascLen / 2];
    int index = 0;
    for (int i = 0; i < (ascLen + 1) / 2; i++) {
      bcd[i] = ascToBcd(ascii[index++]);
      bcd[i] = (byte) (((index >= ascLen) ? 0x00 : ascToBcd(ascii[index++])) + (bcd[i] << 4));
    }
    return bcd;
  }

  /**
   * ASCII码转BCD码. (按照名字猜的用途啊, 应该差不多 !!! 汗颜)
   */
  public static byte ascToBcd(byte asc) {
    byte bcd;

    if ((asc >= '0') && (asc <= '9')) {
      bcd = (byte) (asc - '0');
    } else if ((asc >= 'A') && (asc <= 'F')) {
      bcd = (byte) (asc - 'A' + 10);
    } else if ((asc >= 'a') && (asc <= 'f')) {
      bcd = (byte) (asc - 'a' + 10);
    } else {
      bcd = (byte) (asc - 48);
    }
    return bcd;
  }

  /**
   * BCD转字符串.
   */
  public static String bcd2Str(byte[] bytes) {
    char[] temp = new char[bytes.length * 2];
    char val;

    for (int i = 0; i < bytes.length; i++) {
      val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);
      temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');

      val = (char) (bytes[i] & 0x0f);
      temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
    }
    return new String(temp);
  }

  /**
   * 拆分字符串.
   */
  public static String[] splitString(String string, int len) {
    int xval = string.length() / len;
    int yval = string.length() % len;
    int zval = 0;
    if (yval != 0) {
      zval = 1;
    }
    String[] strings = new String[xval + zval];
    String str = "";
    for (int i = 0; i < xval + zval; i++) {
      if (i == xval + zval - 1 && yval != 0) {
        str = string.substring(i * len, i * len + yval);
      } else {
        str = string.substring(i * len, i * len + len);
      }
      strings[i] = str;
    }
    return strings;
  }

  /**
   * 拆分数组.
   */
  public static byte[][] splitArray(byte[] data, int len) {
    int xval = data.length / len;
    int yval = data.length % len;
    int zval = 0;
    if (yval != 0) {
      zval = 1;
    }
    byte[][] arrays = new byte[xval + zval][];
    byte[] arr;
    for (int i = 0; i < xval + zval; i++) {
      arr = new byte[len];
      if (i == xval + zval - 1 && yval != 0) {
        System.arraycopy(data, i * len, arr, 0, yval);
      } else {
        System.arraycopy(data, i * len, arr, 0, len);
      }
      arrays[i] = arr;
    }
    return arrays;
  }

  /**
   * 测试用 main.
   */
  public static void main(String[] args) throws Exception {
    HashMap<String, Object> map = getKeys();
    // 生成公钥和私钥
    RSAPublicKey publicKey = (RSAPublicKey) map.get("public");
    RSAPrivateKey privateKey = (RSAPrivateKey) map.get("private");

    // 模
    String modulus = publicKey.getModulus().toString();
    System.out.println("pubkey modulus=" + modulus);
    // 公钥指数
    String publicExponent = publicKey.getPublicExponent().toString();
    System.out.println("pubkey exponent=" + publicExponent);
    // 私钥指数
    String privateExponent = privateKey.getPrivateExponent().toString();
    System.out.println("private exponent=" + privateExponent);
    // 明文
    String ming = "111";
    // 使用模和指数生成公钥和私钥
    RSAPublicKey pubKey = RsaUtils.getPublicKey(modulus, publicExponent);
    RSAPrivateKey priKey = RsaUtils.getPrivateKey(modulus, privateExponent);
    // 加密后的密文
    String mi = RsaUtils.encryptByPublicKey(ming, pubKey);
    System.err.println("mi=" + mi);
    // 解密后的明文
    String ming2 = RsaUtils.decryptByPrivateKey(mi, priKey);
    System.err.println("ming2=" + ming2);
  }
}